package com.shiliu.api.common.filter;


import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.shiliu.api.common.exception.ServiceException;
import com.shiliu.api.common.utils.JwtUtil;
import com.shiliu.model.pojo.ResponseResult;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.util.*;

/**
 * @description: 验证token是否合法的过滤器, 只有携带了token的才可以访问API接口
 * @author: shiliu
 * @created: 2019/12/25 21:24
 * @version: v1.0.0
 */
@Slf4j
public class TokenApiFilter implements Filter {

    /**
     * 检验token头部字符串
     */
    private static final String BEARER = "Bearer ";

    /**
     * 不拦截路径的集合,set可去重复
     */
    private static Set<String> exclusionsPattern = new HashSet<>();

    /**
     * 一级路径通配符
     */
    private static final String ONE_WILD_CARD = "*";
    /**
     * 二级路径通配符
     */
    private static final String TWO_WILD_CARD = "**";

    @Override
    public void init(FilterConfig filterConfig) {
        // 获取不过滤路径字符串
        String exclusions = filterConfig.getInitParameter("exclusions");
        // 将不过滤的请求路径存入Set集合中
        if (StringUtils.isNotEmpty(exclusions)) {
            // 将规则路径的中的 空格 和多余的 / 去掉
            String[] split = exclusions.split(",");
            for (int i = 0; i < split.length; i++) {
                split[i] = replaceSeparator(split[i]);
            }
            exclusionsPattern.addAll(Arrays.asList(split));
        }
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 获取请求的路径 /api/v1/login
        String requestURI = request.getRequestURI();
        // 判断是否有不拦截路径,如果有放行
        if (isExclusions(replaceSeparator(requestURI))) {
            filterChain.doFilter(request, response);
            return;
        }
        // 获取请求头中的token信息
        String token = request.getHeader("Authorization");
        if (StringUtils.isNotEmpty(token) && token.startsWith(BEARER)) {
            // 验证JWT信息
            String jwt = token.split(BEARER)[1];
            try {
                // 合法正常通过,非法抛异常
                Claims claims = JwtUtil.parseToken(jwt);
                String userId = claims.get("userId", String.class);
                String user = claims.get("user", String.class);
                //重新生成新的jwt放在Header中,原始token还是有效的,只能和调用方洽谈每次都要覆盖原始token
                String refreshJwt = JwtUtil.generateToken(userId, user, 5 * 60 * 1000);
                response.setHeader("token",refreshJwt);
                // 放行
                filterChain.doFilter(request, response);
                return;
            } catch (ExpiredJwtException e) {
                log.error("jwt时间过期!!" + e.getMessage());
                send(response,"token时间过期!");
            } catch (Throwable e) {
                log.error("jwt非法!!" + e.getMessage());
                send(response,"token非法!");
            }
        }
        send(response,"非法访问,请登录!");
    }


    /**
     * token有异常返回提示信息
     */
    public void send(HttpServletResponse response,String message) {
        BufferedOutputStream bos = null;
        try {
            response.setContentType("application/json;charset=UTF-8");
            ServletOutputStream outputStream = response.getOutputStream();
            bos = new BufferedOutputStream(outputStream);
            ResponseResult result = ResponseResult.error500(message);
            String json = JSON.toJSONString(result);
            bos.write(json.getBytes("UTF-8"));
            bos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * /api/v1/*
     * /api/v1/login
     * <p>
     * 判断是否是不拦截路径 /* : 一级路径  /** : 多级路径
     * 只匹配 [[/api/v1/*,/api/v1/user/*,/api/v1/user/login]] 路径
     *
     * @param requestUri 目标请求路径
     * @return
     */
    public  boolean isExclusions(String requestUri) {
        if (StringUtils.isNotEmpty(requestUri) && exclusionsPattern.size() > 0) {
            Iterator<String> iterator = exclusionsPattern.iterator();
            while (iterator.hasNext()) {
                // 获取Set中的路径规则
                String next = iterator.next();
                // 不拦截,即使配置了.addUrlPatterns("");
                if ("/**".equals(next)) {
                    return true;
                }
                if(requestUri.equals(next)){
                    return true;
                }
                // 规则索引
                int nextIndex = getLastIndex(next);
                // 目标索引
                int uriIndex = getLastIndex(requestUri);
                // 规则字符串
                String nextStr = lastString(next);
                // 目标字符串
                String uriStr = lastString(requestUri);
                // 一级路径
                if (next.endsWith(ONE_WILD_CARD)) {
                    if (uriIndex == nextIndex && nextStr.equals(uriStr)) {
                        return true;
                    }
                }
                // 多级路径
                if (next.endsWith(TWO_WILD_CARD)) {
                    if(nextIndex <= uriIndex && uriStr.contains(nextStr)){
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * 获取字符串中/的最后的索引
     * @param str
     * @return
     */
    public  int getLastIndex(String str) {
        if (str != null && !"".equals(str)) {
            int uriIndex = str.lastIndexOf("/");
            return uriIndex;
        }
        return -1;
    }

    /**
     * 使用 / 截取字符串,否则返回本身
     *
     * @param str
     * @return
     */
    public  String lastString(String str) {
        int lastIndex = getLastIndex(str);
        if (lastIndex != -1) {
            str = str.substring(0, lastIndex);
        }
        return str;
    }

    /**
     * 将路径中多余的 / 去掉 和不小心打的空格
     *
     * @param path
     * @return
     */
    public String replaceSeparator(String path) {
        if (StringUtils.isNotEmpty(path)) {
            String[] split = path.split("/");
            StringBuffer sb = new StringBuffer();
            for (String s : split) {
                if (StringUtils.isNotEmpty(s)) {
                    sb.append("/").append(s.replace(" ", ""));
                }
            }
            return sb.toString();
        }
        return null;
    }
}
